#ifndef PDXCXX_H
#define PDXCXX_H

#include <sstream>
#include <unordered_map>
#include <vector>

#include "clang/AST/AST.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/AST/Mangle.h"

namespace pdxcxx
{
	class ClangParser;

	namespace diagnostic
	{
		enum ID //might need to be in a different header later to avoid recompilation
		{
			OneOrMoreUninitedFields = 1,
			UndefinedSoCantAnalyze = 2,
			UsingDeletedMemory = 3,
		};
	}

	class Diagnostic
	{
	public:
		enum Level
		{
			Disabled,
			Warning,
			Error,
		};
	private:
		typedef std::unordered_map<diagnostic::ID, Level> IDLevelMap;
		static IDLevelMap _levels;
		static int _numErrors;
		static int _numWarnings;

		const diagnostic::ID _id;
		Level _level;
		const clang::SourceManager& _sourceManager;
		std::stringstream _stream; //might become perf issue but let's wait and see
	public:
		Diagnostic(diagnostic::ID id, const clang::SourceManager& _sourceManager)
			: _id(id), _sourceManager(_sourceManager)
		{
			auto it = _levels.find(id);
			if (it != _levels.end()) _level = it->second;
			else _level = Error;
		}

		void cancel() { _level = Disabled; }
		void report();
		static void setLevel(diagnostic::ID id, Level level);

		~Diagnostic()
		{
			report();
		}

		template <typename T>
		Diagnostic& operator <<(const T& arg)
		{
			_stream << arg;
			return *this;
		}
		Diagnostic& operator <<(clang::SourceLocation arg)
		{
			_stream << arg.printToString(_sourceManager);
			return *this;
		}
	};

	class WholeProgram
	{
		ClangParser& _parser; //TODO: would like to get rid of sharing for multithreading purposes, unfortunately relying on mangle context caused slippery slope

		typedef std::vector<std::unique_ptr<clang::ASTContext>> ASTVector;
		ASTVector _ast;

		std::unordered_map<std::string, const clang::FunctionDecl*> _definitions;
	public:
		WholeProgram(ClangParser& parser);

		clang::SourceManager& getSourceManager() const;

		typedef ASTVector::const_iterator ASTIterator;
		ASTIterator astBegin() const { return _ast.begin(); }
		ASTIterator astEnd() const { return _ast.end(); }
		void addAST(std::unique_ptr<clang::ASTContext> ast) { _ast.push_back(std::move(ast)); }

		void addDefinition(const clang::FunctionDecl* def);
		const clang::FunctionDecl* findDefinition(const clang::FunctionDecl* decl) const;
	};

	struct IAnalysis : public clang::ast_matchers::MatchFinder::MatchCallback //dont want children to inherit run, AFAIK
	{
		typedef clang::ast_matchers::MatchFinder::MatchResult MatchResult;

		//constructor model: (WholeProgram&, MatchFinder&)

		virtual void matchAST(const MatchResult& result) {}
		virtual void runAnalysis() {}

		void run(const MatchResult& result) final override;
	};

	class AnalysisList
	{
		typedef std::vector<std::unique_ptr<IAnalysis>> AnalysisVector;

		WholeProgram& _program;
		clang::ast_matchers::MatchFinder& _finder;
		AnalysisVector _analyses;

		template <typename AnalysisT>
		void listAnalysis()
		{
			_analyses.push_back(std::unique_ptr<IAnalysis>(new AnalysisT(_program, _finder)));
		}
	public:
		AnalysisList(WholeProgram& program, clang::ast_matchers::MatchFinder& finder)
			: _program(program), _finder(finder)
		{
		}

		typedef AnalysisVector::iterator AnalysisIterator;
		AnalysisIterator analysisBegin() { return _analyses.begin(); }
		AnalysisIterator analysisEnd() { return _analyses.end(); }

		void listAll();
	};
}

#endif //PDXCXX_H
